一些特别棒的面试题[0]

1.说一下你熟悉的设计模式
我最熟悉的设计模式:工厂模式(ES5),组件设计模式(ES6)
工厂模式(ES5,基于prototype。此例中基类Base,子类Factory)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
var Factory = function () {
if(!(this instanceof Factory)){
return new Factory();
}
}
Factory.prototype = Object.assign(new Base(), {
version: '0.0.1',
defaultOption:{
title:'标题'
},
init:function (cfg) {
this.title = cfg.title || '';
this.currentOption = Object.assign(this.defaultOption,{
//...
})
},
render: function () {
var option = this.currentOption;
this.chart.setOption(option);
},
showTitle: function () {
this._showTitle();
}
})

组件设计模式(ES6,基于class,方便继承和初始化,也是React组件的推荐写法,我比较喜欢。此例中父类Compnent,子类Retrive)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Retrive extends Component {
constructor (props) {
super(props);
this.state = {
name:''
};
this.getRemoteData = this.getRemoteData.bind(this);
}
getRemoteData (data) {
this.state.retriveResult = data;
}
render(){
return (
<div className="Retrive">
<Button name="search" onClick={this.getRemoteData}>查询</Button>
</div>
);
}
}

2.说一下你理解的模块机制
AMD: 异步模块加载规范。
a.js,定义一个依赖jQuery和echrts的组件。

1
2
3
4
5
6
7
8
9
10
11
12
13
define(['jquery', 'echarts'], function ($, echarts) {
var AMD = function(){}
AMD.prototype = {
title:'',
foo: function(){}//AMD类或者继承AMD类的子类的属性
}
function bar(){}//返回,公共属性
function baz(){} //未返回,私有属性
return {
main:AMD,
bar: bar
}
});

如果b.js依赖a.js,可以这样

1
2
3
4
5
6
7
define(['./a'], function (a) {
//调用构造函数,foo
var instance_amd = new a.main();
instance_amd.foo()
//调用bar
a.bar()
});

ES6 modules: 和python的包机制很类似,导入import,导出export。

1.场景:vue,react推荐机制,需要babel转义成es5以兼容浏览器。
2.关于import…(from…)
①.import…from…的from命令后面可以跟很多路径格式,若只给出vue,axios这样的包名,则会自动到node_modules中加载;若给出相对路径及文件前缀,则到指定位置寻找。
②.可以加载各种各样的文件:.js、.vue、.less等等。
③.可以省略掉from直接引入。
3.关于export
①.导出的可以是对象,表达式,函数,类
②.导出是为了让别人导入
4.言外话:使用es6的话,有一个特别好的规范去遵守,airbnb的es6规范(https://github.com/airbnb/javascript)

CommonJS:nodejs中使用较多,关键词是require,没写过node包,只引用过别人的模块,所以内部实现原理不是很清楚。

3.MVVM原理

MVVM是一种软件架构模式,MVVM有助于前后端分离。
View:视图层,粗略理解为DOM。
Model:与数据库对应的model,一般为json格式,作为req的body通过http(s)与数据库实现通信。
ViewModel:View与Model通过ViewModel实现双向绑定。

核心是提供对View和ViewModel的双向数据绑定,这样使得ViewModel的改变View立即变化,MVVM在前端的实现有:angular,vue,react。

vue中的常用数据双向绑定。

1
2
3
view:{{message}}
viewModel v-model="message"
model:message
1
2
3
4
5
6
7
8
9
10
11
<div id="app-6">
<p>{{ message }}</p>
<input v-model="message">
</div>

var app6 = new Vue({
el: '#app-6',
data: {
message: 'Hello Vue!'
}
})

单文件组件中的话,就多了一个用html5的template标签将view和viewModel包裹起来,model部分停留在script标签部分。

1
2
3
4
5
6
7
8
9
10
<template>
view
viewModel
</tamplate>
<script>
model
</script>
<styles>
为了让view好看点
</styles>

react的话,我在使用的过程中,没有听说过双向绑定的东西,对redux reducers推荐写为纯函数印象深刻,纯函数的话,感觉应该有点单项数据流的意思。

既然说到框架了,说一个最让我感觉有趣的点,那就是组件间的通信,对于简单组件,只涉及父子级别的通信的,vue使用on emit的方式,react使用props。对于复杂级别通信,爷爷父亲儿子孙子等等时,vue推荐使用vuex,react推荐使用redux,统一的全局状态树用来做状态管理非常好,可以使得逻辑非常清晰。vue项目文件结构研究不深,react的项目文件结构的话,presentational和containers的设计方法感觉非常有道理,一个负责视图一个负责数据,非常清爽。

4.最熟悉的框架路由机制
vue路由依赖:vue-router
通过组合组件来组成单页应用程序,只需要将组件映射到路由即可。
前端路由的核心,就在于 —— 改变视图的同时不会向后端发出请求。
需要注意2种模式的区别:hash模式和history模式,hash模式会在后面加一个很丑的#,可以开启history去掉。
hash模式原理:它的特点在于:hash 虽然出现在 URL 中,但不会被包括在 HTTP 请求中,对后端完全没有影响,因此改变 hash 不会重新加载页面。hash可以理解为锚点,例如./index.html/#/foo,hash值为#/foo,这样不会跳转页面。就相当于统一页面的不同锚点,页面间跳转与 ./index.html/#foo到./index.html/#bar类似。

./store/index.js

1
2
3
4
5
6
7
8
9
10
import Router from 'vue-router'
Vue.use(Router)
export default new Router({
routes: [
{
path: '/common',
name: 'common',
component: Common
}
]

路由层面还会包括嵌套路由,动态路由以及重定向,相当于自己模仿浏览器请求然后服务器响应模式,其实不涉及向后端请求,仅在浏览器就可以实现页面跳转,前段时间我做的用户权限控制就用到了vue-router,相比MVC结构下的后端路由,清晰了不少,这样后端只要负责路由编写api就好。

5.状态管理
下面是我在用vuex做项目时的一些思考,简单修改了一下,也添加了一些关于redux的思考。

vuex
state,前端data
view,前端DOM
actions,用户操作,引起data变化从而导致DOM变化。

多个组件(视图)共享状态:通俗来讲,就是多个组件间会通信时,导致从后端拿来的数据发生变化,当组件较多时,如果兄弟组件间的通信都依赖父组件进行通信,会导致组件间的耦合非常高,从而导致项目逻辑混乱,难以维护。

多个组件(视图)依赖于同一状态。
来自不同视图的行为需要变更同一状态。

全局单例模式管理,把组件的共享状态抽取出来
不管在组件树的哪个位置,任何组件都能获取状态或者触发行为!

实践出真知:
1.state存放在index.js中,创建的Store实例getter,mutations,actions等,可以分离出来
2.getters存放在getter.js中,数据流为state→getter→组件,getter相当于一个数据获取过滤器,从仓库拿特定数据到组件,相当于对computed的集中处理。
3.mutations存放在mutations.js中,数据流为组件→mutations→state,mutations相当于一个数据提交发射器,从组件提交数据到仓库
4.actions存放在actions.js中,数据流为组件→actions→mutations→state,异步操作的主要场所。
5.modules是开发大型应用时需要用到的,每个module都有单独的states,getters,actions以及mutation,有一股nodejs模块的味道。

vuex三原则:
1.唯一数据源
2.保持状态只读
3.数据改变只能通过纯函数完成 更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。

!!!mutation和action的区别!!!

mutation只变更本地的状态,也就是说,直接只去修改store中的数据。
action包含异步操作,直接调用api,通过api的数据,再提交mutation。

可以说,action只比mutation多了一个异步调用api的操作,因为调用api后,一般有2种返回结果,succes或者error,或者是promise的多种状态,根据不同的。

最近在学习redux,组件dispatch一个action到store,相当于发送一个http请求,然后store做出响应,返回一个response给组件。和vuex大致类似,唯一有区别的是,vuex还需要引入react-redux,引入Provider和connect连接组件和store。

6.统计字符串中单词出现次数
“ hi how are you i am fine thank you youtube am am “,统计”you”出现的次数。

1
2
3
4
5
6
7
8
9
10
11
12
13
function wordCount(str,word){
var str = str || "";
var word = word || "";
var strArr = str.split(" ");
var count = 0;
for(var i=0;i<strArr.length;i++){
if(word===strArr[i]){
count++
}
}
return count;
}
wordCount("hi how are you i am fine thank you youtube am am","you");

如果字符串没有空格怎么办?

1
2
3
4
5
6
7
8
9
10
11
12
13
function wordCount(str,word){
var str = str || "";
var word = word || "";
var count = 0;
var index = str.indexOf(word);
while(index!==-1){
count++;
str = str.substr(index+word.length);
index = str.indexOf(word)
}
return count;
}
wordCount("hihowareyouiamfinethankyouyoutubeamam","you");

如果不用js内置字符串函数,自己用每个字符对比呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
function wordCount(str,word){
var num = 0;
var str = str+" " || "";
var word = word || "";
var strArr = str.split("");
var wordArr = word.split("");
var count = 0;
function compare(arr1,a,arr2,b){
if(b+a<arr2.length){
if(arr1[a]===arr2[b+a]){
num++;
return compare(arr1,a+1,arr2,b+1)
}
if(num===arr1.length){
count++
num = 0;
}
}
}
for(var i=0;i<strArr.length;i++){
for(var j=0;j<wordArr.length;j++){
if(wordArr[wordArr.length-1]===strArr[i+wordArr.length-1]){
compare(wordArr,0,strArr,i+0)
}
}
}
return count;
}
wordCount("hihowareyouiamfinethankyouyoutubeamam","am");

可以更加高效一些吗?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
function wordCount (str,word) {

var str = str+" " || "";
var word = word || "";
var strArr = str.split("");
var wordArr = word.split("");
var wordArrLen = wordArr.length;
var count = 0;
var num = 0;

function compare (arr1,a,arr2,b) {
if(b+a<arr2.length){
if(arr1[a]===arr2[b+a]){
num++;
return compare(arr1,a+1,arr2,b+1)
}
if(num===arr1.length){
count++;
num = 0;
}
}
}

var j = 0;
while(j<wordArrLen){
var i = 0;
while(i<strArr.length){
if(wordArr[wordArrLen -1]===strArr[i+wordArrLen -1]){
compare(wordArr,0,strArr,i+0);
}
i++;
}
j++;
}
return count;
}

wordCount("hihowareyouiamfinethankyouyoutubeamam","a");

//1.调整最高层级遍历数组,从37的2次方降到3的2次方,从1369降到9
//2.合并控制变量和控制条件,使用while替代for,去除JS引擎查询i,j是否存在的消耗,会稍微降低代码可读性
//3.对重复引用的wordArr.length,赋值给局部变量wordArrLen,在这里,Array.prototype.length的查询次数从3次降低到1次